=begin

L'esercizio di oggi consiste nello scrivere una classe Mappa per la
gestione di un gruppo di citta'


Si definisca per prima cosa la classe Citta per rappresentare una singola
citta. Questa classe deve contentere i seguenti metodi (piu qualsiasi altro
metodo riteniate utile).

initialize(nome,xcor,ycor,pop)
  crea una nuova citta
  nome e' una stringa contenente il nome della citta
  xcor e ycor sono due float contenenti le coordinate (nel piano) della citta'
  pop e' un intero che indica la popolazione

to_s
restituisce una stringa contenente nome, coordinate e popolazione di self

distanza(c)
  restistuisce la distanza euclidea tra self e la citta c
  (la distanza si calcola nel solito modo utilizzando le coordinate delle
  citta'; la radice quadrata si calcola con Math.sqrt() )

=end

## classe citta
class Citta
  attr_reader :nome, :xcor, :ycor, :popolazione

  def initialize(n,x,y,pop)
    @nome=n
    @xcor=x
    @ycor=y
    @popolazione=pop
  end

  def distanza(b)
    d2 = (@xcor-b.xcor)**2 +  (@ycor-b.ycor)**2
    Math.sqrt(d2)
  end

  def to_s
    "#@nome (#@xcor,#@ycor)  popolazione: #@popolazione"
  end

  # dato che metteremo le citta in un array e' necessario definire il
  # metodo == (altrimenti i metodi include?, delete, etc non funzionano)
  # Leggendo il testo vediamo che ha senso considerare due citta' uguali
  # se hanno lo stesso nome o le stesse coordinate.
  # Per semplicita' qui consideriamo il nome
  def ==(b)
    b.instance_of?(Citta) && @nome==b.nome
  end

end




=begin

Si definisca poi la classe Mappa per la gestione di un insieme di citta'

La classe deve contenere i seguenti metodi:

initialize()
  crea una mappa vuota (senza citta)

aggiungi_citta(c)
  aggiunge la citta c alla mappa (c deve essere di tipo Citta)
  c deve essere aggiunta solamente se nella mappa non esiste gia'
  una citta con lo stesso nome di c.

  Restituisce true se c viene aggiunta alla mappa, false altrimenti,


cancella_citta(n)
  cancella dalla mappa la citta' di nome n (se esiste)
  Restituisce true se la citta' viene cancellata, false altrimenti,

to_s
  restituisce una stringa contenente le citta contenute in self
  (alla fine di ogni citta' mettete uno \n per non avere tutto su una riga)


popolazione_totale(lis)
  lis e' una lista di nomi di citta'; il metodo deve resituire la somma
  delle popolazioni delle citta' che appaiono in lis e sono nella mappa
  Quindi se scrivo m.popolazione_totale(["Alessandria", "Novara", "Vercelli"])
  e Alessandria e Vercelli sono nella mappa m mentre Novara non  c'e'
  il metodo deve restituire la somma delle popolazioni di Alessandria e
  Vercelli. Se nessuna delle citta' di lis e' nella mappa deve essere
  restituito 0

piu_vicina_tra_popolose(c,pop)
  restituisce la citta' piu' vicina a c (c esclusa) tra quelle della mappa
  che hanno popolazione maggiore di pop

piu_popolosa_tra_vicine(c,dist)
  restituisce la citta' con popolazione maggiore tra quelle della mappa che
  distano dalla citta' c meno di dist (c esclusa)

crea_grafo(dist)
  restituisce un hash che ha come chiavi delle stringhe e come valori degli
  array di stringhe secondo la sequente regola: per ogni citta' c nella mappa
  l'hash deve associare al nome di c (che e' appunto una stringa) la lista
  dei nomi delle citta' che distano da c meno di dist (ecludendo la citta' c).

  Ad esempio, se la mappa contiene le citta' Alessandria, Asti, Novara e
  Vercelli e le distanze tra Alessanria e le altre tre citta' sono
  rispettivamente 15, 25, 20 allora crea_grafo(21) deve restituire un hash
  che associa alla stringa "Alessandria" la lista ["Asti", "Vercelli"].
  Invece crea_grafo(11) deve restituire a hash che associa a "Alessandria" la
  lista vuota [] in quanto non ci sono citta' che distano da Alessandria meno
  di 11. (Naturalmente l'hash deve associare le liste opportune anche alle
  chiavi "Asti", "Novara", e "Vercelli").

  Un altro modo per vedere questo metodo (che spiega anche il suo nome)
  e' osservare che l'hash restituito rappresenta il grafo
  che ha come vertici le citta' della mappa e ha un arco tra due citta'
  se e solo se esse distano meno di dist.



max2passi(n,d)
  dati il nome di una citta' n e la distanza d, questa procedura deve
  restituire una lista contenente tutti i nomi delle citta' diverse
  da n che:
    distano meno di d dalla citta' n
  oppure
    distano meno di d da una che dista meno di d dalla citta' n
  In altre parole, la lista deve contenere i nomi delle citta'
  che sono raggiungibili da n facendo al massimo 2 passi,
  dove per passo si intende percorrere una distanza minore di d
  [Suggerimento: per realizzare questo metodo vi conviene
  per prima cosa chiamare crea_grafo(d). Infatti quello che deve essere
  restituito e' l'elenco delle citta' che in tale grafo sono raggiungibili
  dal vertice n percorrendo al massimo due archi.]


IMPORTANTE 1
  Non fate confusione fra una citta' (un istanza della classe Citta)
  e il nome di una citta' (una stringa). Nella descrizione dei metodi
  ho usato c per indicare un parametro di tipo Citta e d per indicare
  un parametro di tipo stringa che indica il nome di una citta'

IMPORTANTE 2
  Molti di questi metodi richiedono solo 2 righe di codice se fate uso
  dei metodi che prendono come argomento un blocco (in particolare quelli,
  molto potenti, del modulo Enumerable).

Per testare i vostri metodi potete usare la procedura main() fornita qui sotto
il cui output dovrebbe essere simile a quello riportato in fondo a questi file

=end



## classe Mappa
class Mappa
include Comparable

  def initialize()
    @listac = []  # rappresentiamo la mappa come un array di citta'
  end

  def to_s
    ris = ""
    for x in @listac
      ris += x.to_s + "\n"
    end
    return ris
  end

  def aggiungi_citta(c)
    return false if @listac.include?(c) # esiste citta' con lo stesso nome
    @listac << c
    return true
  end

  def cancella_citta(n)
   dim=@listac.size
    @listac.delete_if {|i| n==i.nome}
    if dim==@listac.size
      return false
    else
      return true
    end
   
  end

  def popolazione_totale(lis)
    tot=0
   for x in lis
     @listac.each{|j| if j.nome==x; tot+=j.popolazione end}
   end
   return tot
  end

  # questa l'abbiamo fatta a lezione
  def piu_vicina_tra_popolose(c,pop)
    aux = @listac.select {|a| a.popolazione>pop}
    aux.delete(c)
    return aux.min{|a,b| a.distanza(c) <=> b.distanza(c) }
  end

  def piu_popolosa_tra_vicine(c,dist)
    prov=@listac.find_all{|x| x.distanza(c)<=dist && x.distanza(c)>0}
    return prov.max{|a,b| a.popolazione <=> b.popolazione}
  end

  def crea_grafo(d)
    h=Hash.new{}
      for y in @listac
        h[y.nome]=[]
        tmp=@listac.find_all{|x| y.distanza(x)<=d && y.distanza(x)>0}
        tmp.each{|i| h[y.nome]<<i.nome}
      end
    return h
  end

  def max2passi(n,d)
    ris=[]
    h=crea_grafo(d)
    for i in h[n]
      if (@listac.find{|j| j.nome==n}).distanza(@listac.find{|j| j.nome==i}) < d;  ris<<i; end
      h[i].each{|x| if (@listac.find{|j| j.nome==i}).distanza(@listac.find{|j| j.nome==x}) < d && x!=n
           ris<<x
        end}

    end
    ris.uniq!
    return ris
  end

end





def main()

  puts "============= inizio test =============="

  puts "---- test classe citta ----"
  a = Citta.new("Alfa",0,1,120)
  b = Citta.new("Beta",2,5,280)
  c = Citta.new("Gamma",-1,7,90)

  puts a.distanza(b)
  puts b.distanza(a)
  puts a.distanza(c)
  puts c.distanza(c)

  puts "---- creazione mappa ----"
  m = Mappa.new()
  puts m.aggiungi_citta(a)
  puts m.aggiungi_citta(b)
  puts m.aggiungi_citta(c)
  puts m.aggiungi_citta(b)

  puts m.cancella_citta("Zorro")
  puts m.cancella_citta(b.nome)
  puts m.cancella_citta(b.nome)
  puts m.cancella_citta("Gamma")

  # queste sono tutte inserzioni che hanno successo
  puts m.aggiungi_citta(b)
  puts m.aggiungi_citta(c)
  puts m.aggiungi_citta(Citta.new("Delta",2,2,130))
  puts m.aggiungi_citta(Citta.new("Epsilon",-3,2.5,13))
  puts m.aggiungi_citta(Citta.new("Zeta",0,10,1010))
  puts m.aggiungi_citta(Citta.new("Teta",-1,-5,200))
  puts m.aggiungi_citta(Citta.new("Kappa",2,0,50))
  puts m.aggiungi_citta(Citta.new("Lambda",-3,0,170))

  puts m
  puts "--- test popolazione totale ----"
  puts m.popolazione_totale(["Alfa","Kappa","Teta"])
  puts m.popolazione_totale(["Epsilon","Tango"])
  puts m.popolazione_totale(["Tango"])
  puts m.popolazione_totale(["Zeta", "Delta"])






 puts "--- test vicine/popolose ----"
  puts m.piu_vicina_tra_popolose(a,200)
  puts m.piu_vicina_tra_popolose(a,80)
  puts m.piu_vicina_tra_popolose(a,8000)
  puts m.piu_popolosa_tra_vicine(c,5)
  puts m.piu_popolosa_tra_vicine(a,4)
  puts m.piu_popolosa_tra_vicine(b,5)
  puts m.piu_popolosa_tra_vicine(b,0.01)
  puts m.piu_popolosa_tra_vicine(Citta.new("Tau",1,-2,999),6)



  puts "---- test grafo ----"
  puts ">>> maxdist=5.1"
  g = m.crea_grafo(5.1)
  for e in g
   puts "#{e[0]} --> #{e[1]} \n"
  end
 puts ">>> maxdist=4"
  g = m.crea_grafo(4)
  for e in g
    puts "#{e[0]} --> #{e[1]} \n"
  end

  puts "---- test max2passi (1) ----"
  puts m.max2passi("Alfa",0.1)
  puts "---- test max2passi (2) ----"
  puts m.max2passi("Alfa",4)
  puts "---- test max2passi (3) ----"
  puts m.max2passi("Teta",4)
  puts "---- test max2passi (4) ----"
  puts m.max2passi("Beta",4)


  puts "==================== fine test =================="




end

main